#include "StdAfx.h"
#include "AIVehicle.h"

#include "GoalOp.h"
#include <IPhysics.h>
#include <ISystem.h>

#include <IConsole.h>
#include "VertexList.h"
#include <vector>
#include <algorithm>
#include "Cry_Vector2.h"

#include <ISerialize.h>


//
//---------------------------------------------------------------------------------------------------------
CAIVehicle::CAIVehicle() : 
m_fNextFiringTime(GetAISystem()->GetFrameStartTime()),
	m_fFiringPauseTime(GetAISystem()->GetFrameStartTime()),
m_fFiringStartTime(GetAISystem()->GetFrameStartTime()),
	m_vDeltaTarget(ZERO),
m_ShootPhase(0),
	m_eDriverInsideCheckResult(eDICR_NotChecked),
	m_ePlayerInsideCheckResult(eDICR_NotChecked),
	m_bDriverInside(false),
	m_bPoweredUp(false)
{
	_fastcast_CAIVehicle = true;

// can't reset now - no parameters are initialized yet
//	Reset();
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::UpdateDisabled(EObjectUpdate type)
{
	CAIActor::UpdateDisabled(type);
  
	m_bDryUpdate = (type == AIUPDATE_DRY);
  AlertPuppets();
	m_eDriverInsideCheckResult = eDICR_NotChecked;
	m_ePlayerInsideCheckResult = eDICR_NotChecked;
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::Update(EObjectUpdate type)
{
	FUNCTION_PROFILER( gEnv->pSystem,PROFILE_AI );
	CCCPOINT(CAIVehicle_Update);

	m_eDriverInsideCheckResult = eDICR_NotChecked;
	m_ePlayerInsideCheckResult = eDICR_NotChecked;

  const SAIBodyInfo& bodyInfo = QueryBodyInfo();

	UpdateBehaviorSelectionTree();

  m_bDryUpdate = (type == AIUPDATE_DRY);

  // make sure to update direction when entity is not moved
  SetPos( bodyInfo.vEyePos );
  SetBodyDir( bodyInfo.GetBodyDir() );
  SetEntityDir( bodyInfo.vEntityDir );
  SetMoveDir( bodyInfo.vMoveDir );

  AlertPuppets();

	UpdateHealthTracking();

	const float dt = GetAISystem()->GetFrameDeltaTime();

	m_damagePartsUpdated = false;
	m_targetDazzlingTime = max(0.f, m_targetDazzlingTime - dt);

  if (!m_bDryUpdate)
  {	
    FRAME_PROFILER("AI system vehicle full update",gEnv->pSystem,PROFILE_AI);

    CTimeValue fCurrentTime = GetAISystem()->GetFrameStartTime();
		m_fTimePassed = (m_fLastUpdateTime.GetSeconds() > 0.f)
			? min(0.5f, (fCurrentTime - m_fLastUpdateTime).GetSeconds())
			: 0.f;

    m_fLastUpdateTime = fCurrentTime;

    m_State.Reset();

    GetStateFromActiveGoals(m_State);

    UpdatePuppetInternalState();
  }
  else
  {
		static const bool doDryUpdateCall = true;
		if (doDryUpdateCall)
  {
    // if approaching then always update
			for (size_t i = 0; i < m_vActiveGoals.size(); ++i)
    {
      QGoal& Goal = m_vActiveGoals[i];
      Goal.pGoalOp->ExecuteDry(this);
    }
  }
	}

  //--------------------------------------------------------
  // Orient towards the attention target always
	CAIObject *pAttentionTarget = m_refAttentionTarget.GetAIObject();
  Navigate(pAttentionTarget);
  if (pAttentionTarget)
  {
    m_State.nTargetType = pAttentionTarget->GetType();
    m_State.bTargetEnabled = pAttentionTarget->IsEnabled();
  }
  else
  {
    m_State.nTargetType = -1;
    m_State.bTargetEnabled = false;
  }

  FireCommand();

  m_vLastMoveDir = m_State.vMoveDir;

    UpdateAlertness();
  }


//
//---------------------------------------------------------------------------------------------------------
Vec3 CAIVehicle::PredictMovingTarget(const CAIObject* pTarget, const Vec3 &vTargetPos, const Vec3 &vFirePos, const float duration, const float distpred )
{
	Vec3 vError(ZERO);

	// if we need a prediction of the target
	if(pTarget)
	{
		if (IPhysicalEntity* pTargetPhysics = pTarget->GetPhysics())
		{
		pe_status_dynamics  dSt;
			pTargetPhysics->GetStatus(&dSt);
			
			if(GetSubType() == CAIObject::STP_HELI)
			{
				vError = dSt.v*duration;
			}
			else
			{
				Vec3 vPrediction  = vTargetPos + dSt.v*duration;
				Vec3 vTmp = vPrediction - vFirePos;
				if (distpred > 0.f)
				{
					float len = vTmp.GetLength();
					vError.z = len /distpred;
				}
			}
		}
	}

	return vError;
}

//
//---------------------------------------------------------------------------------------------------------
Vec3 CAIVehicle::GetError( const Vec3 &vTargetPos ,const Vec3 &vFirePos, const float fAccuracy )
{
	// Add an error to the target position depending on accuracy
	Vec3 vError(ZERO);

	if (fAccuracy < 1.f)
	{
		vError = vFirePos - vTargetPos;
		vError.z = 0.f;
		vError.NormalizeSafe();

		float dist = (vTargetPos - vFirePos).GetLength();
		float fAccuracyScale = 1.f - fAccuracy;
		float rangeRotation = fAccuracyScale * ( (float)(ai_rand() % 1000 )/10.f - 50.f );

		float rangeLength = fAccuracyScale * (
			( (float)(ai_rand() % 100) < fAccuracy * 100.f )
			?  (float)(ai_rand() % 2700) / 100.f
			: (float)(ai_rand() % 1000) / 100.f + 15.f);

		vError *= rangeLength;
		vError *= min(1.5f, dist / 100.f);

		Matrix33 rotmatZ = Matrix33::CreateRotationZ( DEG2RAD(rangeRotation) );
		vError = rotmatZ * vError;

		vError = vError + vTargetPos;
		vError.z = gEnv->p3DEngine->GetTerrainElevation( vError.x, vError.y );
		vError = vError - vTargetPos;
	}

	return vError;

}

//
//---------------------------------------------------------------------------------------------------------
bool CAIVehicle::CheckExplosion(const Vec3& vTargetPos, const Vec3& vFirePos, const Vec3& vActualFireDir, const float fDamageRadius)
{
	// get the hit point of a bullet, then check if there is a friend around the hit point.
	// not to give a damage to same species.

	const Vec3 vDirVector = vTargetPos - vFirePos;
	float fDamageRadius2 = fDamageRadius > 0.f ? square(fDamageRadius) : 1.f;

	PhysSkipList skipList;
	GetPhysicalSkipEntities(skipList);

	ray_hit hit;
	Vec3 vHitPoint;

	if (gAIEnv.pWorld->RayWorldIntersection( vFirePos, vDirVector, COVER_OBJECT_TYPES, HIT_COVER,
		&hit, 1, &skipList[0], skipList.size()))
	{
		// When the bullet will hit an object which is in its way
		vHitPoint = hit.pt;
	}
	else
	{
		// When the bullet will hit the player
		vHitPoint = vTargetPos;
	}

	CAIObject* pTarget = NULL;
	bool bNoFriendsInRadius = NoActorsInRadius2(AIOBJECT_ACTOR, vHitPoint, fDamageRadius2, &pTarget);
	if (bNoFriendsInRadius && !pTarget)
	{
		bNoFriendsInRadius = NoActorsInRadius2(AIOBJECT_PLAYER, vHitPoint, fDamageRadius2, &pTarget);
	if (bNoFriendsInRadius && !pTarget)
		{
			bNoFriendsInRadius = NoActorsInRadius2(AIOBJECT_VEHICLE, vHitPoint, fDamageRadius2, &pTarget);
		}
	}

	Vec3 vActualDir = vActualFireDir;
	vActualDir.SetLength(vDirVector.GetLength());

	if (m_objectSubType != CAIObject::STP_HELI)
	{
		// if will hit the terrain/static and there is no friend
		if (bNoFriendsInRadius)
		{
			if ( gAIEnv.pWorld->RayWorldIntersection(vFirePos, vActualDir, COVER_OBJECT_TYPES, HIT_COVER,
				&hit, 1, &skipList[0], skipList.size()) )
			{
					// and if it is far away from the target.
				if ((vTargetPos - hit.pt).GetLength() > fDamageRadius * 3.f)
				{
					// shouldn't fire
					bNoFriendsInRadius = false;
				}
			}
		}
	}
	else
	{
		if (bNoFriendsInRadius)
		{
			if ( gAIEnv.pWorld->RayWorldIntersection(vFirePos, vActualDir, COVER_OBJECT_TYPES, HIT_COVER,
				&hit, 1, &skipList[0], skipList.size()) )
			{
				if ((vFirePos - hit.pt ).GetLength() < fDamageRadius * 10.f)
				{
					bNoFriendsInRadius = false;
			}
		}
	}
	}

	return	bNoFriendsInRadius;
}

//
// Decide whether to fire or not
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::FireCommand()
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	m_State.fire = eAIFS_Off;
	m_State.aimTargetIsValid = false;

	if (!AllowedToFire())
	{
		m_ShootPhase = 0;
		m_fFiringStartTime = GetAISystem()->GetFrameStartTime();
		return;
	}

	CAIObject* pFireTarget = GetFireTargetObject();

	if (!pFireTarget)
		return;

	if(!GetProxy())
		return;

	// basic parameters
	// m_CurrentWeaponDescriptor is not correct now these parameters below should be given by Descriptor
	// 4/8/2006 Tetsuji

	bool  bCoaxial			= (m_fireMode == FIREMODE_SECONDARY);
	bool  bAAA          = ((m_fireMode == FIREMODE_CONTINUOUS) && (m_objectSubType == CAIObject::STP_CAR));
	float fDamageRadius = bCoaxial ? 0.5f : 8.f;
	float fAccuracy			= m_Parameters.m_fAccuracy;

	const SAIBodyInfo& bodyInfo = GetBodyInfo();

	Vec3	vActualFireDir = bodyInfo.vFireDir;
	Vec3	vFirePos = bodyInfo.vFirePos;

	Vec3 vFwdDir;
	if (IEntity * pEntity = GetEntity())
	{
		Matrix33 worldRotMat( pEntity->GetWorldTM() );
		Vec3 vFwdOrg = Vec3Constants<float>::fVec3_OneY;
		vFwdDir = worldRotMat*vFwdOrg;
		//gEnv->pAISystem->AddDebugLine(vFirePos, vFirePos + vFwdDir * 20.f, 255, 255, 255, 1.f);
	}
	else
	{
		vFwdDir = bodyInfo.vMoveDir;
	}

	Vec3 vMyPos;
	if (IPhysicalEntity* PhysicalEntity = GetPhysics())
	{
		pe_status_pos my_status;
		PhysicalEntity->GetStatus(&my_status);
		vMyPos =my_status.pos;
	}
	else
	{
		vMyPos = bodyInfo.vEyePos;
	}

	Vec3	vTargetPos;
	{
		// Use the fire target position directly instead of
		// the offset calculated below. It's a better reference
		// for determining if the cannon is pointing directly
		// at the target, and therefore if it's possible to fire.
		//
		// I left the code here as a reference, or for future changes.
		//
		// - Jonas Johansson, Crytek Frankfurt, 2011-05-17

// 		IPhysicalEntity *pAttPhys	= pFireTarget->GetPhysics();
// 		if ( pAttPhys )
// 		{
// 			pe_status_pos target_status;
// 			pAttPhys->GetStatus( &target_status );
// 			vTargetPos = target_status.pos;
// 			Vec3 ofs = target_status.BBox[0] + target_status.BBox[1];
// 			ofs.x *=0.5f;
// 			ofs.y *=0.5f;
// 			ofs.z *=0.25f;
// 			vTargetPos +=ofs;
// 		}
// 		else
//		{
			vTargetPos = pFireTarget->GetPos();
//		}
		}

	float targetHeight	= vTargetPos.z - gEnv->p3DEngine->GetTerrainElevation( vTargetPos.x, vTargetPos.y );
	float fireHeight	= vFirePos.z - gEnv->p3DEngine->GetTerrainElevation( vFirePos.x, vFirePos.y );

	// For the helicopter/vtol missiles
	if (m_objectSubType == CAIObject::STP_HELI)
	{
		if ( m_ShootPhase == 0 )
		{
			fDamageRadius = 2.f;
			m_vDeltaTarget = GetError( vTargetPos, vFirePos, fAccuracy );
			m_vDeltaTarget += PredictMovingTarget(pFireTarget, vTargetPos, vFirePos, 1.f, 0.f);
			m_vDeltaTarget += vTargetPos;

			Vec3	vTargetDir = m_vDeltaTarget - vFirePos;
			Vec3	vUnitTargetDir = vTargetDir;
			// If the target is not in front of him. can't fire.
			
			vUnitTargetDir.NormalizeSafe();
			//gEnv->pAISystem->AddDebugLine(vFirePos, vFirePos + vUnitTargetDir * 20.f , 255, 0, 0, 1.f);

			float	fDifference = vUnitTargetDir.Dot(vFwdDir);
			if (fDifference < cry_cosf(DEG2RAD(30.f)))
			{
				m_fFiringStartTime = GetAISystem()->GetFrameStartTime();
				return;
			}

			// if a missile pass by near a target
			Vec3 vTmp = vFwdDir.Cross( vTargetDir );
			float d = vTmp.GetLength();
			Vec3 vNormalizedTargetDir = vTargetDir;
			vNormalizedTargetDir.NormalizeSafe();
			float dot = vNormalizedTargetDir.Dot( vFwdDir );
			if (d > 12.f || (dot > DEG2RAD(10.f) && d > 10.f))
			{
				m_fFiringStartTime = GetAISystem()->GetFrameStartTime();
				return;
			}

			// it also need to be vertical to a wing vector.
			// this check is needed because firing position is not accurate.26/06/2006 tetsuji
			// check if there is the same specie around a target position

			if ( m_fireMode != FIREMODE_FORCED )
			{
				if (!CheckExplosion(m_vDeltaTarget, vFirePos, vUnitTargetDir, fDamageRadius))
				{
					m_fFiringStartTime = GetAISystem()->GetFrameStartTime();
					return;
				}
				else
				{
					m_ShootPhase = 1;
				}
			}
			else
			{
				m_ShootPhase = 1;
			}
		}
		if ( m_ShootPhase )
		{
			// finalize a result and check the time. 
			CTimeValue firingDuration;

			firingDuration.SetSeconds(0.f);
			firingDuration += m_fFiringStartTime;
			if ( GetAISystem()->GetFrameStartTime() < firingDuration )
				return;

			m_State.vLookTargetPos = m_vDeltaTarget;
			m_State.vShootTargetPos = m_vDeltaTarget;
			m_State.vAimTargetPos = m_vDeltaTarget;
			m_State.aimTargetIsValid = true;
			m_State.fire = eAIFS_On;
			m_ShootPhase = 0;
		}
		return;
	}

	// For the warrior, 
	if (fireHeight > 10.f)
	{

		if ( GetAISystem()->GetFrameStartTime() < m_fNextFiringTime )
		{
			m_State.fire = eAIFS_On;
		}
		else
		{
			m_State.vLookTargetPos = vTargetPos;
			m_State.vAimTargetPos = vTargetPos;
			m_State.aimTargetIsValid = true;
			m_State.vShootTargetPos = vTargetPos;
			m_State.fire = eAIFS_On;
			CTimeValue firingDuration;
			firingDuration.SetSeconds(3.f);
			firingDuration += m_fFiringStartTime;
			m_fNextFiringTime = firingDuration;
		}

		return;
	}

	// For tank Coaxial Gun
	if (bCoaxial || bAAA)
	{
		Vec3	vTargetDir = vTargetPos - vFirePos;
		//After the rotation of the tunnet has been completed,
		float	fDuration;
		float	fBoxRange;
		float	fMaxDot;

		if (bAAA)
		{
			fDuration = 2.f;
			fBoxRange = 30.f;
			fMaxDot = 30.f;

			PredictMovingTarget(pFireTarget, vTargetPos, vFirePos, 1.f, 30.f);
			if (vTargetDir.GetLength() < 20.f)
			{
				m_State.vLookTargetPos = vTargetPos;
				m_State.vAimTargetPos = vTargetPos;
				m_State.aimTargetIsValid = true;
				m_State.vShootTargetPos = vTargetPos;
				return;
			}
		}
		else
		{
			Vec3 vTargetDirFromCenter = vTargetPos - vMyPos;
			Vec3 vTargetDirFromFirePos = vTargetPos - vFirePos;
			Vec3 vCenterToFirePos = vFirePos - vMyPos;
			vTargetDirFromCenter.z = vTargetDirFromFirePos.z = vCenterToFirePos.z = 0.f;
			float distanceFromCenter = vTargetDirFromCenter.GetLength();
			float distanceFromFirePos = vTargetDirFromFirePos.GetLength();
			float distanceFromCenterToFire = vCenterToFirePos.GetLength();
			fDuration = 4.f;
			fBoxRange = 5.f;
			fMaxDot = (distanceFromCenter < 15.f) ? 120.f : 15.f;
			if ( distanceFromCenter < distanceFromCenterToFire + 1.5f )
			{
				return;
			}
		}

		Vec3	vTmp = vActualFireDir.Cross( vTargetDir );
		float	d = vTmp.GetLength();

		vTargetDir.NormalizeSafe();
		float	inner = vActualFireDir.Dot( vTargetDir );

		bool allowFiring;

		//if there is still a big difference between acutal fire direction and ideal fire direction,
		if( d > fBoxRange || inner < cry_cosf(DEG2RAD(fMaxDot)) || GetAISystem()->GetFrameStartTime() >= m_fNextFiringTime)
		{
			m_fNextFiringTime.SetSeconds(fDuration);
			m_fNextFiringTime += GetAISystem()->GetFrameStartTime();

			m_State.vLookTargetPos = vTargetPos;
			m_State.vAimTargetPos = vTargetPos;
			m_State.aimTargetIsValid = true;
			m_State.vShootTargetPos = vTargetPos;
			allowFiring = false;
		}
		else
		{
			if (!bAAA)
			{
				vTargetPos = pFireTarget->GetPos();
				vTargetDir = vTargetPos - vFirePos;
				vTargetPos.z -=0.3f;
				m_State.vLookTargetPos = vTargetPos;
				m_State.vAimTargetPos = vTargetPos;
				m_State.aimTargetIsValid = true;
				m_State.vShootTargetPos = vTargetPos;
			}

			allowFiring = true;
		}

		UpdateTargetTracking( GetWeakRef(pFireTarget), m_State.vAimTargetPos);

		if (allowFiring && GetAISystem()->GetFrameStartTime() < m_fNextFiringTime )
		{
			if (AdjustFireTarget(pFireTarget, m_State.vShootTargetPos, CanDamageTarget(), 0.5f,
				DEG2RAD(30.0f), &m_State.vShootTargetPos))
				m_State.fire = eAIFS_On;
		}

		return;
	}

	// For the tank cannon
	// in this part, we wait until the turret is moving then shoot
	{

		if ( m_ShootPhase == 0 )		//aiming set up
		{
			if (targetHeight < 10.f)
			{
				m_vDeltaTarget = GetError( vTargetPos, vFirePos, fAccuracy );
				m_vDeltaTarget += PredictMovingTarget(pFireTarget, vTargetPos, vFirePos, 1.f, 0.f);
				m_vDeltaTarget += vTargetPos;

			}
			++m_ShootPhase;

			CTimeValue tValue;
			tValue.SetSeconds(3.f);
			m_fFiringPauseTime = GetAISystem()->GetFrameStartTime() + tValue;
		}
		else if (  m_ShootPhase == 1 )	//aiming ( the turret is rotating )
		{
			m_State.vLookTargetPos = m_State.vAimTargetPos = m_State.vShootTargetPos = m_vDeltaTarget;

			//After the rotation of the turret has been completed, 
			Vec3	vTargetDir = m_vDeltaTarget - vFirePos;

			vTargetDir.NormalizeSafe();
			vActualFireDir.NormalizeSafe();

			float	inner = vActualFireDir.Dot( vTargetDir );
			float	cosval = cry_cosf(DEG2RAD(3.f));
			if( inner > cosval  || m_fFiringPauseTime < GetAISystem()->GetFrameStartTime() )
			{
				CTimeValue tValue;
				tValue.SetSeconds(0.5f);
				m_fFiringPauseTime = GetAISystem()->GetFrameStartTime() + tValue;
				m_ShootPhase ++;
			}
		}
		else if (  m_ShootPhase == 2 )	 //fire ( the turret is fixed )
		{
			m_State.vLookTargetPos = m_State.vAimTargetPos = m_State.vShootTargetPos = m_vDeltaTarget;
			if( m_fFiringPauseTime < GetAISystem()->GetFrameStartTime() )
			{
				CTimeValue tValue;
				tValue.SetSeconds(0.5f);
				m_fFiringPauseTime = GetAISystem()->GetFrameStartTime() + tValue;
				m_ShootPhase ++;
			}
		}
		else if (  m_ShootPhase == 3 )	 //fire ( the turret is fixed )
		{
			m_State.vLookTargetPos = m_State.vAimTargetPos = m_State.vShootTargetPos = vActualFireDir * 10.f + vFirePos; // fix the turret

			CTimeValue tValue;
			tValue.SetSeconds(3.f);

			if ( m_fFiringPauseTime < GetAISystem()->GetFrameStartTime() )
			{
				if( CheckTargetInRange( m_vDeltaTarget ) &&  m_fFiringPauseTime + tValue > GetAISystem()->GetFrameStartTime() )
				{
					Vec3	vTargetDir = m_vDeltaTarget - vFirePos;
					Vec3	vTmp = vActualFireDir.Cross( vTargetDir );
					float	d = vTmp.GetLength();
					float	distanceToTheTarget = vTargetDir.GetLength();
					vTargetDir.NormalizeSafe();
					float	inner = vActualFireDir.Dot( vTargetDir );

					if ((d < 30.f) && (inner > cry_cosf(DEG2RAD(30.f))))
					{
						if ((m_fireMode == FIREMODE_FORCED) || CheckExplosion(vActualFireDir * distanceToTheTarget + vFirePos, vFirePos, vActualFireDir, fDamageRadius))
						{
							m_State.fire = eAIFS_On;
							return;
						}
					}
					else
					{
						tValue.SetSeconds(3.f);
					}
				}
				else
				{
					m_ShootPhase = 0;
				}
			}
		}
	}
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::ParseParameters(const AIObjectParams &params, bool bParseMovementParams)
{
	CPuppet::ParseParameters(params, bParseMovementParams); // Also calls CAIActor::ParseParams

	m_Parameters = params.m_sParamStruct;
}

//
//---------------------------------------------------------------------------------------------------------
EntityId CAIVehicle::GetPerceivedEntityID() const
{
	// Use the driver if one is present
	EntityId driverId = GetDriverEntityId();
	return (driverId != 0) ? driverId : CPuppet::GetPerceivedEntityID();
}

//
//---------------------------------------------------------------------------------------------------------
// 
void CAIVehicle::Reset(EObjectResetType type)
{
	if (type == AIOBJRESET_INIT)
	{
		SetObserver(false); // TODO(Marcio): clean this up
	}

	CPuppet::Reset(type);

	CAISystem* pAISystem = GetAISystem();

	m_fNextFiringTime = pAISystem->GetFrameStartTime();
	m_fFiringStartTime = pAISystem->GetFrameStartTime();
	m_fFiringPauseTime = pAISystem->GetFrameStartTime();
	m_bPoweredUp = false;
	m_ShootPhase = 0;

	m_bDriverInside = false;
	m_vDeltaTarget.zero();
}

//
//---------------------------------------------------------------------------------------------------------
// 
void CAIVehicle::Event(unsigned short eType, SAIEVENT* pAIEvent)
{
	CAISystem* pAISystem = GetAISystem();
	switch( eType )
	{
		case AIEVENT_AGENTDIED:
			m_bEnabled = false;
			pAISystem->NotifyEnableState(this, m_bEnabled);
			SetObserver(false);
		SetDriverInside(false);
			m_State.ClearSignals();
		break;

		case AIEVENT_DRIVER_IN:
		SetDriverInside(true);
		return;		// vehicle-specific event

		case AIEVENT_DRIVER_OUT:
		SetDriverInside(false);
		return;		// vehicle-specific event

		case AIEVENT_DISABLE:
			m_bEnabled = false;
			pAISystem->NotifyEnableState(this, m_bEnabled);
			SetObserver(false);
		SetDriverInside(false);
		return;

		case AIEVENT_ENABLE:
			SetObserver(true);
		// Cannot just enable the vehicle; it has to have a driver inside
		SetDriverInside(m_bDriverInside);
			return;
	}

	CPuppet::Event(eType, pAIEvent);
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::SetDriverInside(bool bDriverInside)
{
	m_bDriverInside = bDriverInside;

	if (bDriverInside)
	{
		CAIActor* pDriver = GetDriver();
		m_bEnabled = pDriver && pDriver->IsEnabled();
	}
	else
	{
		m_bEnabled = false;
	}

	GetAISystem()->NotifyEnableState(this, m_bEnabled);
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::Navigate(CAIObject *pTarget)
	{
	CCCPOINT(CAIVehicle_Navigate);
	
	m_State.vForcedNavigation = m_vForcedNavigation;
	m_State.fForcedNavigationSpeed = m_fForcedNavigationSpeed;

	if (m_bLooseAttention)
	{
		CAIObject *pLooseAttentionTarget = m_refLooseAttentionTarget.GetAIObject();
		if (pLooseAttentionTarget)
			pTarget = pLooseAttentionTarget;
		else
			pTarget = NULL;
		}

	Vec3 vDir, vTargetPos;
	float fTime = GetAISystem()->GetFrameDeltaTime();
//	int TargetType = AIOBJECT_NONE;

	if (pTarget)
	{
		const SAIBodyInfo& bodyInfo = GetBodyInfo();

		vTargetPos = pTarget->GetPos();
		vDir = vTargetPos - bodyInfo.vFirePos;

		if(GetSubType() == CAIObject::STP_HELI)
		{
			// (MATT) I'm very dubious about this code {2009/02/04}
			if (m_refLooseAttentionTarget.IsValid())
				m_State.vLookTargetPos = vTargetPos;
			else
				m_State.vLookTargetPos.zero();

		}
		else if ( vDir.GetLength() > 5.0 && !AllowedToFire() )
		{
			Vec3 check1(m_State.vLookTargetPos - bodyInfo.vFirePos );
			Vec3 check2(vTargetPos  - bodyInfo.vEyePos );
			check1.NormalizeSafe();
			check2.NormalizeSafe();
			if ( check1.Dot(check2) < cosf( DEG2RAD(5.0f) ) )
			{
  				m_State.vLookTargetPos = m_State.vShootTargetPos = m_State.vAimTargetPos = bodyInfo.vEyePos + vDir*10.f;
			}
		}
	}
	else
	{
		if(GetSubType() == CAIObject::STP_HELI)
		{
			m_State.vLookTargetPos.zero();
		}
		else if (!AllowedToFire())
		{
			const SAIBodyInfo& bodyInfo = GetBodyInfo();
			m_State.vLookTargetPos = m_State.vShootTargetPos = m_State.vAimTargetPos = bodyInfo.vEyePos + bodyInfo.vFireDir*10.f;;
		}
	}
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::AlertPuppets()
{
	FUNCTION_PROFILER( GetISystem(),PROFILE_AI );

	if (m_objectSubType != CAIObject::STP_CAR)
		return;

	IPhysicalEntity* pPhysicalEntity = GetPhysics();
	if (!pPhysicalEntity)
		return;

	pe_status_dynamics  dSt;
	pPhysicalEntity->GetStatus(&dSt);
	Vec3 v = dSt.v;
	v.z = 0.f;

	const float coeff = 0.4f;
	float fv = coeff * v.GetLength();
	if (fv < 0.5f)
		return;

	Vec3 vn = v;
	vn.Normalize();

	ActorLookUp& lookUp = *gAIEnv.pActorLookUp;
	lookUp.Prepare(ActorLookUp::Position | ActorLookUp::Proxy);

	IEntity* pVehicleEntity = GetEntity();

	// Find the vehicle rectangle to avoid
	AABB localBounds;
	pVehicleEntity->GetLocalBounds(localBounds);
	const Matrix34& tm = pVehicleEntity->GetWorldTM();
	SAIRect3 r;
	GetFloorRectangleFromOrientedBox(tm, localBounds, r);

	// Extend the box based on velocity.
	Vec3 vel = dSt.v;
	const float lookAheadTime = 3.f;
	float speedu = r.axisu.Dot(vel) * lookAheadTime;
	float speedv = r.axisv.Dot(vel) * lookAheadTime;
	
	if (speedu > 0.f)
		r.max.x += speedu;
	else
		r.min.x += speedu;

	if (speedv > 0.f)
		r.max.y += speedv;
	else
		r.min.y += speedv;

	if (IsPlayerInside())
	{
		r.min.y -= 1.f;
		r.max.y += 1.f;
	}

	float avoidOffset = (r.max.x - r.min.x) / 2.f + 0.5f;

	static bool debugdraw = false;

	if (debugdraw)
	{
		const size_t maxpts = 4;
		Vec3 rect[maxpts];
		rect[0] = r.center + r.axisu * r.min.x + r.axisv * r.min.y;
		rect[1] = r.center + r.axisu * r.max.x + r.axisv * r.min.y;
		rect[2] = r.center + r.axisu * r.max.x + r.axisv * r.max.y;
		rect[3] = r.center + r.axisu * r.min.x + r.axisv * r.max.y;
		size_t last = maxpts - 1;
		for (size_t i = 0; i < maxpts; ++i)
		{
			GetAISystem()->AddDebugLine(rect[last], rect[i], 255,0,0, 0.2f);
			last = i;
		}
	}

	const float distToPathThr = (r.max.x - r.min.x)/2 + 0.5f;
	const float dangerRange = max(r.min.GetLength(), r.max.GetLength()) * 1.5f;
	const float dangerRangeSq = square(dangerRange);

	size_t activeCount = lookUp.GetActiveCount();
	
	for (size_t actorIndex = 0; actorIndex < activeCount; ++actorIndex)
	{
		CAIActor* pAIActor = lookUp.GetActor<CAIActor>(actorIndex);
		CPuppet* pPuppet = pAIActor->CastToCPuppet();
		if (!pPuppet || (pPuppet->GetType() == AIOBJECT_VEHICLE))
			continue;

		// Skip distant vehicles
		if (Distance::Point_Point2DSq(lookUp.GetPosition(actorIndex), r.center) > dangerRangeSq)
		{
			if(pPuppet->GetAvoidedVehicle() == this)
			{
				if (pPuppet->GetVehicleAvoidingTime() > 3000)
				{
					CCCPOINT(CAIVehicle_AlertPuppets_SignalVehicle);
					pPuppet->SetAvoidedVehicle(NILREF);
					pPuppet->SetSignal(1, "OnEndVehicleDanger", pVehicleEntity);
				}
			}

			continue;
		}

		// Skip puppets in vehicles.
		if (lookUp.GetProxy(actorIndex)->GetLinkedVehicleEntityId())
			continue;

		Vec3 puppetDir = pPuppet->GetPos() - r.center;
		
		float x = r.axisu.Dot(puppetDir);
		float y = r.axisv.Dot(puppetDir);

		Vec3 pathPosOut(ZERO);
		float distToPath = 0.f;

		bool bNearPath = false;
		if (!m_Path.Empty())
		{
			float distAlongPath = 0.f;
			distToPath = m_Path.GetDistToPath(pathPosOut, distAlongPath, pPuppet->GetPos(), dangerRange, false);//false ->2D only
			if (distToPath <= distToPathThr + pPuppet->GetParameters().m_fPassRadius)
			{
				bNearPath = true;
		}
		}

		const float pr = pPuppet->GetParameters().m_fPassRadius;

		if (bNearPath || (m_Path.Empty() && (x >= (r.min.x - pr)) && (x <= (r.max.x + pr)) && (y >= (r.min.y - pr)) && (y <= (r.max.y + pr))))
		{
			Vec3 move(ZERO);
			if (bNearPath && (distToPath > 0.1f) && Distance::Point_Point2DSq(pPuppet->GetPos(), pathPosOut) > square(0.1f))
			{
				Vec3 norm = pPuppet->GetPos() - pathPosOut;
				norm.z = 0.f;
				norm.Normalize();
				move = norm * (avoidOffset + 2.f - distToPath);
			}
			else 
			{
				move = r.axisv * (avoidOffset + 2.f - fabsf(y));
				if (y < 0.f)
					move = -move;
			}

			// Note: There used to be a CAISystem.IsPathWorth() check here, but it was removed because of the huge performance peaks.
			// Now instead the stick has larger end accuracy (that is, does not need to find exact target location).
			Vec3 avoidPos = pPuppet->GetPos() + move;
			if (debugdraw)
			{
				GetAISystem()->AddDebugSphere(pPuppet->GetPos()+Vec3(0,0,2), 0.25f, 255,255,255, 0.2f);
				GetAISystem()->AddDebugLine(pPuppet->GetPos(), avoidPos, 255,255,255, 0.2f);
				GetAISystem()->AddDebugLine(avoidPos - Vec3(0,0,10), avoidPos + Vec3(0,0,10), 255,255,255, 0.2f);
			}

			if (pPuppet->GetAvoidedVehicle() != this)
			{
				AISignalExtraData* pData = new AISignalExtraData;
				pData->point2 = avoidPos;
				pData->point = vn;
				pPuppet->SetSignal(1, "OnVehicleDanger", pVehicleEntity, pData);
			}

			pPuppet->SetAvoidedVehicle(GetWeakRef(this));

			// Update pos.
			CAIObject* pAvoidTarget = pPuppet->GetOrCreateSpecialAIObject(AISPECIAL_VEHICLE_AVOID_POS);
			pAvoidTarget->SetPos(avoidPos, vn);
			
		}
		else
		{
			if (pPuppet->GetAvoidedVehicle() == this)
		{
			//check rotation around Z axis (2D)
				if (!(fabs(dSt.w.z) > 0.2f && (fv > 0.7f))) // if vehicle is not spinning like a tank
			{
				if (pPuppet->GetVehicleAvoidingTime() > 3000)
				{
					pPuppet->SetAvoidedVehicle(NILREF);
						pPuppet->SetSignal(1, "OnEndVehicleDanger", pVehicleEntity);
					}
				}
			}
		}
	}
}

//
//---------------------------------------------------------------------------------------------------------
void CAIVehicle::Serialize(TSerialize ser)
{
	CPuppet::Serialize(ser);

	ser.BeginGroup( "AIVehicle" );
	{
		ser.Value("m_bDriverInside",m_bDriverInside);
		ser.Value("m_bPoweredUp",m_bPoweredUp);
		ser.Value("m_ShootPhase", m_ShootPhase);
		ser.Value("m_vDeltaTarget",m_vDeltaTarget);

		if(ser.IsReading())
		{
			CAISystem* pAISystem = GetAISystem();
		
			m_fNextFiringTime = pAISystem->GetFrameStartTime();
			m_fFiringStartTime = pAISystem->GetFrameStartTime();
			m_fFiringPauseTime = pAISystem->GetFrameStartTime();
		}
	}
	ser.EndGroup();
}

//
//---------------------------------------------------------------------------------------------------------
bool CAIVehicle::NoActorsInRadius2(short int objectType, Vec3& hitPosition, float fDamageRadius2, CAIObject** pTarget)
{
	*pTarget = NULL;

	AIObjectOwners::iterator ai = gAIEnv.pAIObjectManager->m_Objects.find(objectType);
	AIObjectOwners::iterator aiend = gAIEnv.pAIObjectManager->m_Objects.end();

	while(ai!=aiend)
	{
		// (MATT)	Strong, so always valid {2009/03/25}
		CAIObject *pObject = ai->second.GetAIObject();
		if(pObject->GetType()!=objectType)
			break;
		if ( pObject->IsEnabled() )
		{
			Vec3 dir = pObject->GetPos() - hitPosition;
			float	dist2 = dir.GetLengthSquared();
			if(dist2<fDamageRadius2)
			{
				CAIActor* pActor = pObject->CastToCAIActor();
				if( pActor && IsHostile(pObject, false) == false )
				{
					if( GetEntityID()!=pObject->GetEntityID() )
					{
						*pTarget = NULL;
						return false;
					}
				}
				else
					*pTarget = pObject;
			}
		}
		++ai;
	}
	return true;
}

//
//------------------------------------------------------------------------------------------------------------------
bool CAIVehicle::HandleVerticalMovement( const Vec3& targetPos )
{

	if(m_bPoweredUp)
		return false;

	m_State.vMoveDir = Vec3Constants<float>::fVec3_OneZ;

	Vec3 diff = targetPos - GetPos();
	if (diff.z < 5.f)
	{
		m_bPoweredUp = true;
		return false;
	}

	m_State.fDesiredSpeed = 5.f;
	
	return true;
}

//
//------------------------------------------------------------------------------------------------------------------
bool CAIVehicle::IsDriverInside() const
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	if (m_bEnabled)
		return true;

	if (m_eDriverInsideCheckResult == eDICR_NotChecked)
	{
		m_eDriverInsideCheckResult = (GetDriverEntityId() != 0) ? eDICR_Inside : eDICR_Outside;
	}

	return (m_eDriverInsideCheckResult == eDICR_Inside);
}


//
//------------------------------------------------------------------------------------------------------------------
bool CAIVehicle::IsPlayerInside()
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);

	if (m_bEnabled)
		return true;

	if (m_ePlayerInsideCheckResult == eDICR_NotChecked)
	{
		CAIActor *pDriver = GetDriver();
		m_ePlayerInsideCheckResult = (pDriver && (pDriver->GetType() == AIOBJECT_PLAYER)) ? eDICR_Inside : eDICR_Outside;
	}

	return m_ePlayerInsideCheckResult == eDICR_Inside;
}


//
//------------------------------------------------------------------------------------------------------------------
EntityId CAIVehicle::GetDriverEntityId() const
{
	IAIActorProxy* pAIActorProxy = GetProxy();
	return pAIActorProxy ? pAIActorProxy->GetLinkedDriverEntityId() : 0;
}


//
//------------------------------------------------------------------------------------------------------------------
CAIActor* CAIVehicle::GetDriver() const
{
	const EntityId driverId = GetDriverEntityId();
	if (driverId > 0)
	{
		if (IEntity* pDriverEntity = gEnv->pEntitySystem->GetEntity(driverId))
		{
			if (IAIObject* pDriverAI = pDriverEntity->GetAI())
			{
				return pDriverAI->CastToCAIActor();
			}
	}
	}

	return NULL;
}
